package org.de.maeyer.sampler;

import static java.lang.Double.*;
import static java.util.Arrays.*;
import static org.de.maeyer.util.ArrayUtils.*;

import org.apache.commons.lang.mutable.MutableDouble;
import org.de.maeyer.statistic.Accumulator;
import org.de.maeyer.statistic.Category;
import org.de.maeyer.statistic.DoubleCategory;
import org.de.maeyer.statistic.LinkedDoubleCategory;
import org.de.maeyer.statistic.StatisticsBuilder;

/**
 * @author Peter De Maeyer
 */
public class DoubleHistogramAccumulator implements Accumulator {
	
	private static final int[] EMPTY_MODE_CATEGORY_INDEXES = new int[0];
	
	protected final double[] bounds;
	
	protected final long[] counts;
	
	public DoubleHistogramAccumulator(double[] bounds) {
		this(bounds, new long[bounds.length + 1]);
	}
	
	public DoubleHistogramAccumulator(double[] bounds, long[] counts) {
		this.bounds = bounds;
		this.counts = counts;
	}

	@Override
    public void accumulate() {
	    throw new UnsupportedOperationException();
    }

	@Override
    public void accumulateDouble(double value) {
		int index = binarySearch(bounds, value);
		if (index >= 0) {
			counts[index + 1]++;
		} else {
			counts[~index]++;
		}
    }

	@Override
    public void accumulateLong(long value) {
		accumulateDouble(value);
    }

	@Override
    public void accumulateObject(Object value) {
		accumulateDouble(((Number) value).doubleValue());
    }

	@Override
    public void appendTo(StatisticsBuilder builder) {
		Category[] categories = new Category[counts.length];
		int[] modeCategoryIndexes = EMPTY_MODE_CATEGORY_INDEXES;
		DoubleCategory nextCategory = null;
		MutableDouble modeDensity = new MutableDouble(0.);
	    for (int categoryIndex = categories.length; --categoryIndex != -1; ) {
	    	long count = counts[categoryIndex];
	    	double inclusiveLowerBound = categoryIndex == 0 ? NEGATIVE_INFINITY : bounds[categoryIndex - 1];
	    	DoubleCategory category = nextCategory == null ? new DoubleCategory(inclusiveLowerBound, count) : new LinkedDoubleCategory(inclusiveLowerBound, count, nextCategory);
	    	categories[categoryIndex] = category;
	    	modeCategoryIndexes = updateModeCategoryIndexes(modeCategoryIndexes, modeDensity, category.getDensity(), categoryIndex);
	    	nextCategory = category;
	    }
	    builder.setHistogram(asList(categories), modeCategoryIndexes);
    }

	@Override
    public void reset() {
		fill(counts, 0L);
    }
	
	private static int[] updateModeCategoryIndexes(int[] modeCategoryIndexes, MutableDouble modeDensity, double density, int categoryIndex) {
		if (density > 0.) {
			if (density > modeDensity.doubleValue()) {
				if (modeCategoryIndexes.length < 1 || modeCategoryIndexes.length > 1) {
					modeCategoryIndexes = new int[1];
				}
				modeCategoryIndexes[0] = categoryIndex;
				modeDensity.setValue(density);
			} else if (density == modeDensity.doubleValue()) {
				modeCategoryIndexes = add(modeCategoryIndexes, categoryIndex);
			}
		}
		return modeCategoryIndexes;
	}
}
